using System;

namespace Axe.Collections
{
    [Serializable]
    public class RangeDiapasone : IComparable<RangeDiapasone>
    {
        private int lowerBound, upperBound;

        #region public int Size

        public int Size
        {
            get { return UpperBound - LowerBound + 1; }
        }

        #endregion

        #region public int LowerBound

        /// <summary>
        /// Gets or sets the lower bound.
        /// </summary>
        /// <value>The lower bound.</value>
        public int LowerBound
        {
            get { return lowerBound; }
            set
            {
                if (value > UpperBound)
                    throw new ArgumentOutOfRangeException("Lower Bound cannot be greater than upper bound");

                lowerBound = value;
            }
        }

        #endregion

        #region public int UpperBound

        /// <summary>
        /// Gets or sets the upper bound.
        /// </summary>
        /// <value>The upper bound.</value>
        public int UpperBound
        {
            get { return upperBound; }
            set
            {
                if (value < LowerBound)
                    throw new ArgumentOutOfRangeException("Upper bound cannot be less than lower bound");

                upperBound = value;
            }
        }

        #endregion

        #region public bool Contains(int item)

        /// <summary>
        /// Determines whether hash set contains the specified item.
        /// </summary>
        /// <mParam name="item">The item.</mParam>
        /// <returns>
        /// 	<c>true</c> if hash set contains the specified item; otherwise, <c>false</c>.
        /// </returns>
        public bool Contains(int item)
        {
            return (LowerBound <= item) && (item <= UpperBound);
        }

        #endregion

        #region public void Add(int value)

        public void Add(int value)
        {
            if (!CanAdd(value))
                throw new InvalidOperationException("Cannot add value to set");

            LowerBound = Math.Min(LowerBound, value);
            UpperBound = Math.Max(UpperBound, value);
        }

        #endregion

        #region public bool CanAdd(int value)

        public bool CanAdd(int value)
        {
            if (Contains(value))
                return true;

            if (Math.Abs(LowerBound - value) == 1)
                return true;

            if (Math.Abs(UpperBound - value) == 1)
                return true;

            return false;
        }

        #endregion

        #region public RangeDiapasone(int lower_bound, int upper_bound)

        /// <summary>
        /// Initializes a new instance of the <see cref="RangeDiapasone&lt;TKey&gt;"/> class.
        /// </summary>
        /// <mParam name="lower_bound">The lower_bound.</mParam>
        /// <mParam name="upper_bound">The upper_bound.</mParam>
        public RangeDiapasone(int lower_bound, int upper_bound)
        {
            if (lower_bound > upper_bound)
                throw new ArgumentOutOfRangeException("Lower bound is greater than upper bound");

            lowerBound = lower_bound;
            upperBound = upper_bound;
        }

        #endregion

        #region public RangeDiapasone()

        /// <summary>
        /// Initializes a new instance of the <see cref="RangeDiapasone&lt;TKey&gt;"/> class.
        /// </summary>
        public RangeDiapasone()
        {
        }

        #endregion

        #region public override int GetHashCode()

        public override int GetHashCode()
        {
            return lowerBound ^ upperBound;
        }

        #endregion

        #region public static bool CanSplice(RangeDiapasone s1, RangeDiapasone s2)

        public static bool CanSplice(RangeDiapasone s1, RangeDiapasone s2)
        {
            if (s1.Contains(s2.LowerBound) || s1.Contains(s2.UpperBound))
                return true;
            else if (s2.Contains(s1.lowerBound) || s2.Contains(s1.UpperBound))
                return true;
            else
                return Math.Abs(s1.LowerBound - s2.UpperBound) <= 1 ||
                       Math.Abs(s1.UpperBound - s2.LowerBound) <= 1;
        }

        #endregion

        #region public static RangeDiapasone Splice(RangeDiapasone s1, RangeDiapasone s2)

        public static RangeDiapasone Splice(RangeDiapasone s1, RangeDiapasone s2)
        {
            if (!CanSplice(s1, s2))
                throw new InvalidOperationException("Hash sets cannot be spliced");

            int lower = Math.Min(s1.LowerBound, s2.LowerBound);
            int upper = Math.Max(s1.UpperBound, s2.UpperBound);
            RangeDiapasone Set = new RangeDiapasone(lower, upper);
            return Set;
        }

        #endregion

        #region public static bool CanSplit(RangeDiapasone s, int value)

        public static bool CanSplit(RangeDiapasone s, int value)
        {
            if (!s.Contains(value))
                return false;

            if (s.LowerBound == s.LowerBound && s.LowerBound == value)
                return false;

            return true;
        }

        #endregion

        #region public static RangeDiapasone[] SplitAndRemove(RangeDiapasone s, int value)

        public static RangeDiapasone[] SplitAndRemove(RangeDiapasone s, int value)
        {
            if (!s.Contains(value))
                throw new ArgumentOutOfRangeException("value", value, "Specified value is not exitst.");

            RangeDiapasone[] Result = null;

            if (s.LowerBound == value)
            {
                Result = new RangeDiapasone[] {new RangeDiapasone(s.LowerBound + 1, s.UpperBound)};
            }
            else if (s.UpperBound == value)
            {
                Result = new RangeDiapasone[] {new RangeDiapasone(s.LowerBound, s.UpperBound - 1)};
            }
            else
            {
                Result = new RangeDiapasone[]
                    {
                        new RangeDiapasone(s.LowerBound, value - 1),
                        new RangeDiapasone(value + 1, s.UpperBound)
                    };
            }

            return Result;
        }

        #endregion

        #region IComparable<RangeDiapasone> Members

        public int CompareTo(RangeDiapasone other)
        {
            if (LowerBound == other.LowerBound
                && UpperBound == other.UpperBound)
                return 0;

            return LowerBound > other.LowerBound ? 1 : -1;
        }

        #endregion

        #region public override string ToString()

        public override string ToString()
        {
            if (LowerBound == UpperBound)
                return LowerBound.ToString();
            else
                return String.Format("{0}-{1}", LowerBound, UpperBound);
        }

        #endregion

        public void CopyTo(int[] set, int offset)
        {
            for(int i = 0;i < Size;i++)
            {
                set[i + offset] = LowerBound + i;
            }
        }

        public void CopyTo(int[] set, int offset, int count)
        {
            for(int i=0;i< Math.Min(count, Size);i++)
            {
                set[i + offset] = LowerBound + i;
            }
        }
    };
}